/* PPPoS Client Example with GSM (tested with Telit GL865-DUAL-V3)

   This example code is in the Public Domain (or CC0 licensed, at your option.)

   Unless required by applicable law or agreed to in writing, this
   software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
   CONDITIONS OF ANY KIND, either express or implied.
 */
#include <string.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "esp_system.h"
#include "esp_log.h"

#include "driver/uart.h"
#include "driver/gpio.h"
#include "tcpip_adapter.h"
#include "netif/ppp/pppos.h"
#include "netif/ppp/ppp.h"
#include "netif/ppp/pppapi.h"

#include "ppp_ifac.h"
#include "internet_common.h"


#define CONFIG_GSM_DEBUG 

#ifdef CONFIG_GSM_DEBUG
#define GSM_DEBUG 1
#else
#define GSM_DEBUG 0
#endif
#define BUF_SIZE (1024)
#define GSM_OK_Str "OK"
#define PPPOSMUTEX_TIMEOUT 1000 / portTICK_RATE_MS

#define PPPOS_CLIENT_STACK_SIZE 1024*4

#define PPPOS_STATE_CONNECTING	1
#define PPPOS_STATE_SUCCESSED	2
#define PPPOS_STATE_FAILED		3

// shared variables, use mutex to access them
// static uint8_t gsm_status = GSM_STATE_FIRSTINIT;
// static int do_pppos_connect = 1;
// static uint8_t pppos_task_started = 0;

// local variables
const char PPP_User[20] = "";
const char PPP_Pass[20] = "";
static int uart_num = UART_NUM_1;

// static uint8_t PPPoS_initialized = 0;
static uint8_t PPPoS_keep_running = 1;
static uint8_t PPPoS_STATE = 0;

// The PPP control block
static ppp_pcb *ppp = NULL;

// The PPP IP interface
struct netif ppp_netif;

static const char *TAG = "[PPPOS CLIENT]";

typedef struct
{
	char		*cmd;
	uint16_t	cmdSize;
	char		*cmdResponseOnOk;
	uint16_t	timeoutMs;
	uint16_t	delayMs;
}GSM_Cmd;

static GSM_Cmd cmd_AT =
{
	.cmd = "AT\r\n",
	.cmdSize = sizeof("AT\r\n")-1,
	.cmdResponseOnOk = GSM_OK_Str,
	.timeoutMs = 3000,
	.delayMs = 0,
};

// static GSM_Cmd cmd_Reset =
// {
// 	.cmd = "AT+CFUN=1,1\r\n",
// 	.cmdSize = sizeof("AT+CFUN=1,1\r\n")-1,
// 	.cmdResponseOnOk = GSM_OK_Str,
// 	.timeoutMs = 300,
// 	.delayMs = 0,
// 	.skip = 0,
// };

// static GSM_Cmd cmd_RFOn =
// {
// 	.cmd = "AT+CFUN=1\r\n",
// 	.cmdSize = sizeof("ATCFUN=1,0\r\n")-1,
// 	.cmdResponseOnOk = GSM_OK_Str,
// 	.timeoutMs = 10000,
// 	.delayMs = 1000,
// 	.skip = 0,
// };

static GSM_Cmd cmd_EchoOff =
{
	.cmd = "ATE0\r\n",
	.cmdSize = sizeof("ATE0\r\n")-1,
	.cmdResponseOnOk = GSM_OK_Str,
	.timeoutMs = 300,
	.delayMs = 0,
};

static GSM_Cmd cmd_Pin =
{
	.cmd = "AT+CPIN?\r\n",
	.cmdSize = sizeof("AT+CPIN?\r\n")-1,
	.cmdResponseOnOk = "CPIN: READY",
	.timeoutMs = 1000,
	.delayMs = 0,
};

static GSM_Cmd cmd_Reg =
{
	.cmd = "AT+CREG?\r\n",
	.cmdSize = sizeof("AT+CREG?\r\n")-1,
	.cmdResponseOnOk = "CREG: 0,1",
	.timeoutMs = 3000,
	.delayMs = 2000,
};

static GSM_Cmd cmd_CSQ =
{
	.cmd = "AT+CSQ\r\n",
	.cmdSize = sizeof("AT+CSQ\r\n")-1,
	.cmdResponseOnOk = "+CSQ:",
	.timeoutMs = 3000,
	.delayMs = 2000,
};

static GSM_Cmd cmd_APN =
{
	.cmd = NULL,
	.cmdSize = 0,
	.cmdResponseOnOk = GSM_OK_Str,
	.timeoutMs = 8000,
	.delayMs = 0,
};

static GSM_Cmd cmd_Connect =
{
	.cmd = "AT+CGDATA=\"PPP\",1\r\n",
	.cmdSize = sizeof("AT+CGDATA=\"PPP\",1\r\n")-1,
	// .cmd = "ATDT*99***1#\r\n",
	// .cmdSize = sizeof("ATDT*99***1#\r\n")-1,
	.cmdResponseOnOk = "CONNECT",
	.timeoutMs = 30000,
	.delayMs = 1000,
};

static GSM_Cmd *GSM_Init[] =
{
		&cmd_AT,
		// &cmd_Reset,
		&cmd_EchoOff,
		// &cmd_RFOn,
		// &cmd_NoSMSInd,
		&cmd_Pin,
		&cmd_Reg,
		&cmd_CSQ,
		// &cmd_APN,
		// &cmd_Connect,
};

static GSM_Cmd *GSM_Connect[] = {
		&cmd_APN,
		&cmd_Connect,	
};

#define GSM_InitCmdsSize  (sizeof(GSM_Init)/sizeof(GSM_Cmd *))
#define GSM_ConnectCmdsSize  (sizeof(GSM_Connect)/sizeof(GSM_Cmd *))


// PPP status callback
//--------------------------------------------------------------
static void ppp_status_cb(ppp_pcb *pcb, int err_code, void *ctx)
{
	struct netif *pppif = ppp_netif(pcb);
	LWIP_UNUSED_ARG(ctx);

	switch(err_code) {
		case PPPERR_NONE: {
			ESP_LOGI(TAG,"status_cb: Connected");
			#if PPP_IPV4_SUPPORT
			ESP_LOGI(TAG,"   ipaddr    = %s", ipaddr_ntoa(&pppif->ip_addr));
			ESP_LOGI(TAG,"   gateway   = %s", ipaddr_ntoa(&pppif->gw));
			ESP_LOGI(TAG,"   netmask   = %s", ipaddr_ntoa(&pppif->netmask));
			#endif

			net_info_t temp_eth;
			temp_eth.net_interface = NET_INTERFACE_4G;
			temp_eth.ip = pppif->ip_addr.u_addr.ip4;
			temp_eth.gateway = pppif->gw.u_addr.ip4;
			temp_eth.netmask = pppif->netmask.u_addr.ip4;
			set_net_info(&temp_eth);

			#if PPP_IPV6_SUPPORT
			ESP_LOGI(TAG,"   ip6addr   = %s", ip6addr_ntoa(netif_ip6_addr(pppif, 0)));
			#endif
			// xSemaphoreTake(pppos_mutex, PPPOSMUTEX_TIMEOUT);
			// gsm_status = GSM_STATE_CONNECTED;
			// xSemaphoreGive(pppos_mutex);

			PPPoS_STATE = PPPOS_STATE_SUCCESSED;

			break;
		}
		case PPPERR_PARAM: {
			ESP_LOGE(TAG,"status_cb: Invalid parameter");
			break;
		}
		case PPPERR_OPEN: {
			ESP_LOGE(TAG,"status_cb: Unable to open PPP session");
			break;
		}
		case PPPERR_DEVICE: {
			ESP_LOGE(TAG,"status_cb: Invalid I/O device for PPP");
			break;
		}
		case PPPERR_ALLOC: {
			ESP_LOGE(TAG,"status_cb: Unable to allocate resources");
			break;
		}
		case PPPERR_USER: {
			/* ppp_free(); -- can be called here */
			ESP_LOGW(TAG,"status_cb: User interrupt (disconnected)");
			break;
		}
		case PPPERR_CONNECT: {
			ESP_LOGE(TAG,"status_cb: Connection lost");
			break;
		}
		case PPPERR_AUTHFAIL: {
			ESP_LOGE(TAG,"status_cb: Failed authentication challenge");
			break;
		}
		case PPPERR_PROTOCOL: {
			ESP_LOGE(TAG,"status_cb: Failed to meet protocol");
			break;
		}
		case PPPERR_PEERDEAD: {
			ESP_LOGE(TAG,"status_cb: Connection timeout");
			break;
		}
		case PPPERR_IDLETIMEOUT: {
			ESP_LOGE(TAG,"status_cb: Idle Timeout");
			break;
		}
		case PPPERR_CONNECTTIME: {
			ESP_LOGE(TAG,"status_cb: Max connect time reached");
			break;
		}
		case PPPERR_LOOPBACK: {
			ESP_LOGE(TAG,"status_cb: Loopback detected");
			break;
		}
		default: {
			ESP_LOGE(TAG,"status_cb: Unknown error code %d", err_code);
			break;
		}
	}
}

// === Handle sending data to GSM modem ===
//------------------------------------------------------------------------------
static u32_t ppp_output_callback(ppp_pcb *pcb, u8_t *data, u32_t len, void *ctx)
{
	uint32_t ret = uart_write_bytes(uart_num, (const char*)data, len);
    uart_wait_tx_done(uart_num, 10 / portTICK_RATE_MS);
    if (ret > 0) {
		//写数据成功
    }
    return ret;
}

//---------------------------------------------------------
static void infoCommand(char *cmd, int cmdSize, char *info)
{
#if GSM_DEBUG	    
	char buf[cmdSize+2];
	memset(buf, 0, cmdSize+2);

	for (int i=0; i<cmdSize;i++) {
		if ((cmd[i] != 0x00) && ((cmd[i] < 0x20) || (cmd[i] > 0x7F))) buf[i] = '.';
		else buf[i] = cmd[i];
		if (buf[i] == '\0') break;
	}
	ESP_LOGI(TAG,"%s [%s]", info, buf);
#endif    
}

static int atCmd_waitResponse(char * cmd, char *resp, int cmdSize, int timeout, char *response, int size)
{
	char data[256] = {0};
    int len = 0;

	// ** Send command to GSM
	vTaskDelay(100 / portTICK_PERIOD_MS);
	uart_flush(uart_num);

	if (cmd != NULL) {
		if (cmdSize == -1) 
            cmdSize = strlen(cmd);
		infoCommand(cmd, cmdSize, "AT COMMAND:");
		uart_write_bytes(uart_num, (const char*)cmd, cmdSize);
		uart_wait_tx_done(uart_num, 100 / portTICK_RATE_MS);
	}

	if (response != NULL) 
	{
		memset(response,0,size);
		len=uart_read_bytes(uart_num, (uint8_t*)response, size, timeout / portTICK_RATE_MS);
		if (len >0) 
        {
			response[len] = 0;

			ESP_LOGI(TAG,"AT RESPONSE: [%s]", response);						
		
			return len;
		}
		else
		{
			return 0;
		}
		
	}

    // ** Wait for and check the response
	memset(data, 0, 256);
	uint32_t index = 0;
	uint32_t start_tick = xTaskGetTickCount();
	while(xTaskGetTickCount() - start_tick <= timeout)
	{
		len = uart_read_bytes(uart_num, (uint8_t*)data+index, size-index-1, 10 / portTICK_RATE_MS);
		index += len;

		if (strstr(data, resp) != NULL) {
			ESP_LOGI(TAG,"AT RESPONSE: [%s]", data);
		
			return 1;
		}

		if(index >= sizeof(data))
			break;
	}

	ESP_LOGI(TAG,"AT BAD RESPONSE: [%s]", data);

	return 0;
}

static int atWaitFor_success(GSM_Cmd* cmd,uint8_t tryCount)
{
	int res = 0;
	int count = 0;
	while(res==0 && count < tryCount)
	{
		res = atCmd_waitResponse(cmd->cmd,cmd->cmdResponseOnOk,cmd->cmdSize,cmd->timeoutMs, NULL, 0);
		count ++;
	}

	return res;
}


static int check_module_insterted()
{
	int nfail = 0;
	int ret = 0;

	if(atCmd_waitResponse("AT\r\n", GSM_OK_Str, 4, 1000, NULL, 0))
	{
		return 1;
	}

	ESP_LOGI(TAG,"CONNTED,TO Disconnect");

	printf("\n");
	while(ret == 0)
	{
		if(nfail > 9)
			return 0;

        vTaskDelay(1000 / portTICK_PERIOD_MS);
        uart_flush(uart_num);
        uart_write_bytes(uart_num, "+++", 3);
        uart_wait_tx_done(uart_num, 10 / portTICK_RATE_MS);
        vTaskDelay(1000 / portTICK_PERIOD_MS);

		ret = atCmd_waitResponse("AT\r\n", GSM_OK_Str, 4, 1000, NULL, 0);
		nfail++;

		printf("\rCheck 4G MODULE insterted %d/10",nfail);

	}

	return 1;

}

//返回1，表示重启成功，
//返回0，表示失败(模块没有插入)
static int module_restart()
{
	gpio_set_level(MODULE_RESET, 0);
	vTaskDelay(250);
	gpio_set_level(MODULE_RESET, 1);

	return 1;


}


/*
 * PPPoS TASK
 * Handles GSM initialization, disconnects and GSM modem responses
 */
//-----------------------------
static void pppos_client_task()
{
    static GSM_Cmd *pGSMCmd;

	ESP_LOGI(TAG,"GSM initialization start");
	vTaskDelay(500 / portTICK_PERIOD_MS);

	int gsmCmdIter = 0;
	int nfail = 0;
	// * GSM Initialization loop
	while(gsmCmdIter < GSM_InitCmdsSize)
	{
		pGSMCmd = GSM_Init[gsmCmdIter];

		if(!atWaitFor_success(pGSMCmd,15))
		{
			goto exit;
		}
		else
		{
			gsmCmdIter++;
		}
		
	}

	//AT指令获取IMSI，根据IMSI获取运营商
	nfail = 0;
	char imsi[256] = {0};
	char PPP_ApnATReq[50] = {0};
	int start_index = 0;
	while(1)
	{
		atCmd_waitResponse("AT+CIMI\r\n",NULL,strlen("AT+CIMI\r\n"),1000,imsi,sizeof(imsi));

		if(strstr(imsi,GSM_OK_Str))
		{
			//成功获取到IMSI号
			start_index = strstr(imsi,"460")-imsi;
			
			imsi[start_index+5] = 0;

			ESP_LOGI(TAG,"IMSI: [%s]", imsi); 

			if(	!strcmp(imsi+start_index+3,"00") || !strcmp(imsi+start_index+3,"02") || 
				!strcmp(imsi+start_index+3,"04") || !strcmp(imsi+start_index+3,"07"))
			{
				//移动
				sprintf(PPP_ApnATReq, "AT+CGDCONT=1,\"IP\",\"%s\"\r\n", CHINAMOBILE_APN);
			
				cmd_APN.cmd = PPP_ApnATReq;
				cmd_APN.cmdSize = strlen(PPP_ApnATReq);
			}
			else if(!strcmp(imsi+start_index+3,"01") || !strcmp(imsi+start_index+3,"06") || !strcmp(imsi+start_index+3,"09"))
			{
				//联通
				sprintf(PPP_ApnATReq, "AT+CGDCONT=1,\"IP\",\"%s\"\r\n", CHINAUNICOM_APN);
			
				cmd_APN.cmd = PPP_ApnATReq;
				cmd_APN.cmdSize = strlen(PPP_ApnATReq);
			}
			else
			{
				//电信
				sprintf(PPP_ApnATReq, "AT+CGDCONT=1,\"IP\",\"%s\"\r\n", CHINATELECOM_APN);
			
				cmd_APN.cmd = PPP_ApnATReq;
				cmd_APN.cmdSize = strlen(PPP_ApnATReq);				
			}

			break;
		}
		else
		{
			//未获取到IMSI号
			nfail++;
		
			if(nfail > 3)
				goto exit;
		}
	}	

	gsmCmdIter = 0;
	// * GSM Connect loop
	while(gsmCmdIter < GSM_ConnectCmdsSize)
	{

		pGSMCmd = GSM_Connect[gsmCmdIter];

		if(!atWaitFor_success(pGSMCmd,10))
		{
			goto exit;
		}
		else
		{
			gsmCmdIter++;
		}
	}

	ESP_LOGI(TAG,"GSM initialized.");

	// if(PPPoS_initialized == false)
	// {
		// ** After first successful initialization create PPP control block
		ppp = pppapi_pppos_create(&ppp_netif,
				ppp_output_callback, ppp_status_cb, NULL);

		if (ppp == NULL) {
			ESP_LOGE(TAG, "Error initializing PPPoS");
			goto exit; // end task
		}
	// 	PPPoS_initialized = true;
	// }

	//netif_set_default(&ppp_netif);

	pppapi_set_default(ppp);
	pppapi_set_auth(ppp, PPPAUTHTYPE_PAP, PPP_User, PPP_Pass);
	// pppapi_set_auth(ppp, PPPAUTHTYPE_NONE, PPP_User, PPP_Pass);

	pppapi_connect(ppp, 0);

	// PPPoS_successed = 1;

	uint8_t data[BUF_SIZE] = {0};

	PPPoS_keep_running = true;

	// *** LOOP: Handle GSM modem responses & disconnects ***
	while(PPPoS_keep_running)
	{
		// === Handle data received from GSM ===
		memset(data, 0, BUF_SIZE);
		int len = uart_read_bytes(uart_num, (uint8_t*)data, BUF_SIZE, 10 / portTICK_RATE_MS);
		if (len > 0)	{
			pppos_input_tcpip(ppp, (u8_t*)data, len);
		}

	}  // Handle GSM modem responses & disconnects loop

	pppapi_close(ppp,1);
exit:
	if (ppp)
	{
        ppp_free(ppp);
		ppp = NULL;
	} 


	PPPoS_STATE = PPPOS_STATE_FAILED;

	ESP_LOGE(TAG, "PPPoS TASK TERMINATED");
	vTaskDelete(NULL);
}

//=============
int ppposInit(void* args)
{
	ESP_LOGI(TAG,"START PPPoS");
	static bool uart_initialized = false;
	if(uart_initialized == false)
	{
		gpio_set_direction(UART_GPIO_TX, GPIO_MODE_OUTPUT);
		gpio_set_direction(UART_GPIO_RX, GPIO_MODE_INPUT);
		gpio_set_pull_mode(UART_GPIO_RX, GPIO_PULLUP_ONLY);
		
		uart_config_t uart_config = {
				.baud_rate = UART_BDRATE,
				.data_bits = UART_DATA_8_BITS,
				.parity = UART_PARITY_DISABLE,
				.stop_bits = UART_STOP_BITS_1,
				.flow_ctrl = UART_HW_FLOWCTRL_DISABLE
		};

		//Configure UART1 parameters
		uart_param_config(uart_num, &uart_config);
		//Set UART1 pins(TX, RX, RTS, CTS)
		uart_set_pin(uart_num, UART_GPIO_TX, UART_GPIO_RX, UART_PIN_NO_CHANGE, UART_PIN_NO_CHANGE);
		uart_driver_install(uart_num, BUF_SIZE * 2, BUF_SIZE * 2, 0, NULL, 0);

		uart_initialized = true;
	}

	ESP_LOGI(TAG,"START module restart");
	module_restart();

	vTaskDelay(3000);

	if(!check_module_insterted())
	{
		printf("\nNO 4G MODULE INSIDE\n");
		return 0;
	}

	printf("\n4G MODULE INSIDE\n");

	PPPoS_STATE = PPPOS_STATE_CONNECTING;

    // if (tcpip_adapter_initialized == 0) {
    //     tcpip_adapter_init();
    //     tcpip_adapter_initialized = 1;
    // }

	ESP_LOGI(TAG,"start pppos client task");

	uint32_t start_tick = xTaskGetTickCount();	

    xTaskCreate(&pppos_client_task, "pppos_client_task", PPPOS_CLIENT_STACK_SIZE, NULL, 10, NULL);

	while(PPPoS_STATE == PPPOS_STATE_CONNECTING)//超过100s没有连上，则视为PPPoS拨号失败
	{
		if(xTaskGetTickCount() - start_tick > 100000)
		{
			PPPoS_keep_running = 0;
			return 0;
		}

		vTaskDelay(1000);
	}




	if(PPPoS_STATE == PPPOS_STATE_FAILED)
		return 0;

	return 1;
}


void ppposStop()
{
	PPPoS_keep_running = false;
}

